{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "参考 ：https://zhuanlan.zhihu.com/p/42938173"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "%matplotlib inline \n",
    "\n",
    "def draw_hist(img):\n",
    "    plt.hist(img.ravel(),256,[0,256]);\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "heading_collapsed": true
   },
   "source": [
    "# 综述"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "hidden": true
   },
   "source": [
    "|算法 | 优点| 缺点|\n",
    "|:-----|:-------|:----|\n",
    "|光流场法| 可适用摄像机静止和运动两种场合| 计算量大,易受光照变化影响\n",
    "帧间差分法| 原理简单,计算量小;  适用于实时系统| 检测目标不完整\n",
    "背景减法|算法复杂度小,提取目标完整| 背景模型需要实时更新"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "hidden": true
   },
   "source": [
    "| |背景差分法| 帧间差分法 |光流法\n",
    "|:---|:----|:-----|:-----|\n",
    "运算结果 |运动目标的整个区域|运动目标的外轮廓| 运动目标的整个区域\n",
    "运算复杂度|由背景建模算法的复杂程度决定| 小| 大\n",
    "适用范围 | 摄像头固定(背景相对固定)|摄像头固定(背景相对固定)| 摄像头固定摄像头固定/运动,与背景信息无关\n",
    "鲁棒性| 较好|好|差\n",
    "\n",
    "总结： 光流场法计算复杂， 不适用于实时监控系统；帧差法计算简单，但是检测结果不完整；背景减法效果较好，然而建立一个良好的背景模型需要花费很大计算量和存储量的开销。\n",
    "\n",
    "在对视频或图像序列进行分析时，运动目标检测的主要任务是：\n",
    "\n",
    "- 判断视频图像序列中每一帧有无对应的前景目标\n",
    "- 能否将对应的前景目标的特征提取出来，并将前景目标显示出来。\n",
    "只有检测到了前景目标才能对其进行跟踪与后续的相关分析处理，因此目标检测是目标跟踪处理的基础。局部遮挡、光照等因素都会影响目标检测的精度。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 背景减除\n",
    "\n",
    "是由当前输入图像片中的像素与背景图像片像素相减得到两者差异的区域，背景指的是去除运动目标那张图像片，所得区域即为需要检测的运动目标区域。\n",
    "\n",
    "![pic](../assets/dynamic_detect2.jpg)\n",
    "\n",
    "在很多基础应用中背景减除都是一个非常重要的步骤。例如顾客统计，使用一个静态摄像头来记录进入和离开房间的人数，或者是交通摄像头，需要提取交通工具的信息等。在所有的这些例子中，首先要将人或车单独提取出来。技术上来说，我们需要从静止的背景中提取移动的前景。  \n",
    "\n",
    "  如果你有一张背景（仅有背景不含前景）图像，比如没有顾客的房间，没有交通工具的道路等，那就好办了。我们只需要在新的图像中减去背景就可以得到前景对象了。但是在大多数情况下，我们没有这样的（背景）图像，所以我们需要从我们有的图像中提取背景。如果图像中的交通工具还有影子的话，那这个工作就更难了，因为影子也在移动，仅仅使用减法会把影子也当成前景。真是一件很复杂的事情。\n",
    "  \n",
    "背景减法利用图像序列中的当前帧和事先确定的背景参考模型间的差异比较，来确定运动物体位置，是一种基于统计学原理的运动目标检测的方法。这种方法的性能取决于背景建模技术，Gloyer等人使用单高斯模型的思路，但常常不能准确地描述背景模型。\n",
    "\n",
    "1999年Stauffer等人提出了经典的混合高斯背景建模法，这种方法不仅对复杂场景的适应强，而且能通过自动计算的模型参数来对背景模型调整，虽然增加了高斯分布的个数，造成计算量增大，但检测速度很快，且检测准确，容易实现。基于混合高斯模型建模的背景减法已是运动检测的**主流方法**。OpenCv中有三种三种比较容易使用的方法。\n",
    "## 1. BackgroundSubtractorMOG\n",
    "    \n",
    "这是一个以混合高斯模型为基础的前景/背景分割算法。它是KadewTraKuPong和R.Bowden在2001年提出的。它使用K（K=3或5）个高斯分布混合对\n",
    "背景像素进行建模。使用这些颜色（在整个视频中）存在时间的长短作为混合的权重。背景的颜色一般持续的时间最长，而且更加静止。一个像素怎么会有分布呢？在x，y平面上一个像素就是一个像素没有分布，但是我们现在讲的背景建模是基于时间序列的，因此每一个像素点所在的位置在整个时间序列中就会有很多值，从而构成一个分布。\n",
    "\n",
    "  在编写代码时，我们需要使用函数：`cV2.createBackgroundSubtractorMOGO`创建一个背景对象。这个函数有些可选参数，比如要进行建模场景的时间长度，高斯混合成分的数量，阈值等。将他们全部设置为默认值。然后在整个视频中我们是需要使用`backgroundsubtractor.apply()`就可以得到前景的掩模\n",
    "了。\n",
    "\n",
    "### 基础示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "cap = cv2.VideoCapture(0)  #笔记本摄像头\n",
    "\n",
    "fgbg = cv2.bgsegm.createBackgroundSubtractorMOG()\n",
    "# 可选参数 比如 进行建模场景的时间长度 高斯混合成分的数量-阈值等\n",
    "\n",
    "while True:\n",
    "    ret, frame = cap.read()\n",
    "    # 计算前景掩码\n",
    "    fgmask = fgbg.apply(frame)\n",
    "\n",
    "    cv2.imshow('frame', fgmask)\n",
    "    k = cv2.waitKey(1)  #& 0xff\n",
    "    if k == ord('q'):\n",
    "        break\n",
    "\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "heading_collapsed": true
   },
   "source": [
    "### 检测运动目标"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "hidden": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "cap = cv2.VideoCapture(0)\n",
    "\n",
    "fgbg = cv2.bgsegm.createBackgroundSubtractorMOG()\n",
    "\n",
    "# 可选参数 比如 进行建模场景的时间长度 高斯混合成分的数量-阈值等\n",
    "\n",
    "kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))  # 定义结构元素\n",
    "kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 3))\n",
    "color_m = (255, 0, 0)\n",
    "\n",
    "\n",
    "while True:\n",
    "    ret, frame = cap.read()\n",
    "    if not ret:\n",
    "        break\n",
    "    \n",
    "    fgmask = fgbg.apply(frame)\n",
    "    # 二值化\n",
    "    thresh = cv2.threshold(fgmask, 80, 255, cv2.THRESH_BINARY)[1]\n",
    "    th_eroad = cv2.erode(thresh, kernel1, iterations=1)\n",
    "    th_dilate = cv2.dilate(th_eroad, kernel2, iterations=2)\n",
    "    # 查找检测物体的轮廓,只检测外轮廓,只需4个点来保存轮廓信息\n",
    "    contours_m, hierarchy_m = cv2.findContours(th_dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    for c in contours_m:\n",
    "        if cv2.contourArea(c) < 300:\n",
    "            continue\n",
    "        (x, y, w, h) = cv2.boundingRect(c)\n",
    "        cv2.rectangle(frame, (x, y), (x + w, y + h), color_m, 2)\n",
    "\n",
    "    cv2.imshow(\"source\", frame)\n",
    "    cv2.imshow(\"apply\", fgmask)\n",
    "    cv2.imshow('th_eroad', th_eroad)\n",
    "    cv2.imshow('th_dilate', th_dilate)\n",
    "    k = cv2.waitKey(20)  #& 0xff\n",
    "    if k == ord('q'):\n",
    "        break\n",
    "\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. BackgroundSubtractorMOG2\n",
    "\n",
    "这个也是以高斯混合模型为基础的背景/前景分割算法。它是以2004年和2006年Z.Zivkovic的两篇文章为基础的。这个算法的一个**特点是它为每一个像素选择一个合适数目的高斯分布。（上一个方法中我们使用是K高斯分布）**。**这样就会对由于亮度等发生变化引起的场景变化产生更好的适应。**\n",
    "\n",
    "和前面一样我们需要创建一个背景对象。但在这里我们我们可以选择是否检测阴影。如果 `detectShadous=True`（默认值），它就会检测并将影子标记出来，但是这样做会**降低处理速度**。影子会被标记为灰色。\n",
    "\n",
    "### 基础示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = cv2.cvtColor(cv2.absdiff(frame, frame), cv2.COLOR_BGR2GRAY)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cv2.imshow('frame', frame)\n",
    "k = cv2.waitKey(3000)  #& 0xff\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "cap = cv2.VideoCapture('D:\\OneDrive - business\\git_clone\\OpenCV-Python-Tutorial-master\\data\\street_car.mp4')\n",
    "# cap = cv2.VideoCapture(0)  #笔记本摄像头\n",
    "\n",
    "fgbg = cv2.createBackgroundSubtractorMOG2(detectShadows=False)\n",
    "while True:\n",
    "    ret, frame = cap.read()\n",
    "    fgmask = fgbg.apply(frame)\n",
    "\n",
    "    cv2.imshow('frame', fgmask)\n",
    "    k = cv2.waitKey(30)  #& 0xff\n",
    "    if k == ord('q'):\n",
    "        break\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. BackgroundSubtractorGMG\n",
    "\n",
    "此算法结合了静态背景图像估计和每个像素的贝叶斯分割。这是2012年Andrew_B.Godbehere,Akihiro Matsukawa和Ken_Goldberg在文章中提出的。\n",
    "\n",
    "它使用前面很少的图像（默认为前120帧）进行**背景建模**。使用了概率前景估计算法（使用贝叶斯估计鉴定前景）。这是一种自适应的估计，新观察到的对象比旧的对象具有更高的权重，**从而对光照变化产生适应**。一些形态学操作如开运算闭运算等被用来除去不需要的噪音。在前几帧图像中你会得到一个黑色窗口。\n",
    "\n",
    "对结果进行形态学**开运算对与去除噪声很有帮助**。\n",
    "\n",
    "### 基础示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "# cap = cv2.VideoCapture('../data/vtest.avi')\n",
    "cap = cv2.VideoCapture(\n",
    "    'D:\\OneDrive - business\\git_clone\\OpenCV-Python-Tutorial-master\\data\\street_car.mp4'\n",
    ")\n",
    "\n",
    "kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))\n",
    "fgbg = cv2.bgsegm.createBackgroundSubtractorGMG()  #前 120 帧\n",
    "\n",
    "counter = 0\n",
    "while True:\n",
    "    ret, frame = cap.read()\n",
    "    fgmask = fgbg.apply(frame)\n",
    "    # 开操作\n",
    "    fgmask_ = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)\n",
    "\n",
    "    cv2.imshow('fgmask', fgmask)\n",
    "    cv2.imshow('MORPH_OPEN', fgmask_)\n",
    "    cv2.imshow('src', frame)\n",
    "\n",
    "    # 前120 帧都是黑的\n",
    "    counter += 1\n",
    "    print(counter, end=',')\n",
    "\n",
    "    k = cv2.waitKey(10)  # & 0xff\n",
    "    if k == ord('q'):\n",
    "        break\n",
    "\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三种背景减除综合对比"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "import imageio\n",
    "\n",
    "cap = cv2.VideoCapture(\"D:\\OneDrive - business\\git_clone\\OpenCV-Python-Tutorial-master\\data\\street_car.mp4\")\n",
    "\n",
    "fgbg1 = cv2.bgsegm.createBackgroundSubtractorMOG()\n",
    "fgbg2 = cv2.createBackgroundSubtractorMOG2()\n",
    "fgbg3 = cv2.bgsegm.createBackgroundSubtractorGMG(60)  # initializationFrames=120\n",
    "\n",
    "kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))\n",
    "\n",
    "# 保存gif参数设置\n",
    "gif1 = 'D:/Video/v1.gif'\n",
    "gif2 = 'D:/Video/v2.gif'\n",
    "gif3 = 'D:/Video/v3.gif'\n",
    "frames1 = []\n",
    "frames2 = []\n",
    "frames3 = []\n",
    "\n",
    "while True:\n",
    "    ret, frame = cap.read()\n",
    "    if not ret:\n",
    "        print('not found')\n",
    "        break\n",
    "    frame = cv2.resize(frame, (400, 400), interpolation=cv2.INTER_CUBIC)\n",
    "\n",
    "    # 前景掩码\n",
    "    fgmask1 = fgbg1.apply(frame)\n",
    "    fgmask2 = fgbg2.apply(frame)\n",
    "    fgmask3 = fgbg3.apply(frame)\n",
    "\n",
    "    fgmask4 = cv2.morphologyEx(fgmask3, cv2.MORPH_OPEN, kernel, iterations=1)  # 形态学开运算\n",
    "\n",
    "    cv2.imshow('MOG', fgmask1)\n",
    "    cv2.imshow('MOG2', fgmask2)\n",
    "    cv2.imshow('GMG', fgmask3)\n",
    "    cv2.imshow('MORPH_OPEN', fgmask4)\n",
    "\n",
    "    # 加入帧\n",
    "    frames1.append(fgmask1)\n",
    "    frames2.append(fgmask2)\n",
    "    frames3.append(fgmask4)\n",
    "\n",
    "    k = cv2.waitKey(50) & 0xff\n",
    "    if k == 27 or k == ord('q'):\n",
    "        break\n",
    "\n",
    "# 保存gif\n",
    "imageio.mimsave(gif1, frames1, 'GIF', duration=1/20)\n",
    "imageio.mimsave(gif2, frames2, 'GIF', duration=1/20)\n",
    "imageio.mimsave(gif3, frames3, 'GIF', duration=1/20)\n",
    "\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 帧间差分法\n",
    "\n",
    "帧差法依据的原则是：当视频中存在移动物体的时候，相邻帧（或相邻三帧）之间在灰度上会有差别，求取两帧图像灰度差的绝对值，则静止的物体在差值图像上表现出来全是0，而移动物体特别是移动物体的轮廓处由于存在灰度变化为非0，当绝对值超过一定阈值时，即可判断为运动目标，从而实现目标的检测功能。 帧间差分法的优点是算法实现简单，程序设计复杂度低；对光线等场景变化不太敏感，能够适应各种动态环境，有着比较强的鲁棒性。缺点是不能提取出对象的完整区域，对象内部有“空洞”，只能提取出边界，边界轮廓比较粗，往往比实际物体要大。对快速运动的物体，容易出现鬼影的现象，甚至会被检测为两个不同的运动物体，对慢速运动的物体，当物体在前后两帧中几乎完全重叠时，则检测不到物体。故该方法一般适用于简单的实时运动检测的情况。 这里介绍二帧法和三帧法的实现。\n",
    "\n",
    "![pic](../assets/dynamic_detect1.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二差分法\n",
    "\n",
    "相邻帧间差分法直接对相邻的两帧图像做差分运算，并取差分运算的绝对值构成移动物体，\n",
    "\n",
    "优点：\n",
    "- 运算快速\n",
    "- 实时性高\n",
    "\n",
    "缺点：\n",
    "- 无法应对光照的突变\n",
    "- 物体间一般具有空洞\n",
    "\n",
    "步骤：\n",
    "\n",
    ">前一帧 | 当前帧 ➡ cv2.absdiff() ➡ 灰度图 ➡ 二值化 ➡ (形态学变换) ➡ 轮廓检测 ➡ 过滤轮廓面积"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.3082306\n"
     ]
    }
   ],
   "source": [
    "# 导入必要的软件包\n",
    "import cv2\n",
    "\n",
    "# 视频文件输入初始化\n",
    "filename = \"D:\\OneDrive - business\\git_clone\\OpenCV-Python-Tutorial-master\\data\\street_car.mp4\"\n",
    "camera = cv2.VideoCapture(filename)\n",
    "\n",
    "# # 视频文件输出参数设置\n",
    "out_fps = 12.0  # 输出文件的帧率\n",
    "fourcc = cv2.VideoWriter_fourcc('M', 'P', '4', '2')\n",
    "out1 = cv2.VideoWriter('D:/video/v1.avi', fourcc, out_fps, (500, 400))\n",
    "\n",
    "# 计算时钟\n",
    "e1 = cv2.getTickCount()\n",
    "\n",
    "# 第一帧进行初始化\n",
    "_, lastFrame = camera.read()\n",
    "lastFrame = cv2.GaussianBlur(lastFrame, (7, 7), 0)\n",
    "\n",
    "\n",
    "# 遍历视频的每一帧\n",
    "while camera.isOpened():\n",
    "    # 读取下一帧\n",
    "    (ret, frame) = camera.read()\n",
    "\n",
    "    # 如果不能抓取到一帧，说明我们到了视频的结尾\n",
    "    if not ret:\n",
    "        break\n",
    "        \n",
    "#     frame = cv2.GaussianBlur(frame, (3, 3), 0)\n",
    "\n",
    "    # 调整该帧的大小\n",
    "#     frame = cv2.resize(frame, (500, 400), interpolation=cv2.INTER_CUBIC)\n",
    "\n",
    "# 计算当前帧和前帧的不同\n",
    "    frameDelta = cv2.absdiff(lastFrame, frame)\n",
    "\n",
    "    # 当前帧设置为下一帧的前帧\n",
    "    lastFrame = frame.copy()\n",
    "\n",
    "    # 结果转为灰度图\n",
    "    thresh = cv2.cvtColor(frameDelta, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "    # 图像二值化\n",
    "    thresh = cv2.threshold(thresh, 25, 255, cv2.THRESH_BINARY)[1]\n",
    "    \n",
    "    #去除图像噪声,先腐蚀再膨胀(形态学开运算)\n",
    "#     thresh = cv2.erode(thresh, None, iterations=1)\n",
    "#     thresh = cv2.dilate(thresh, None, iterations=2)\n",
    "\n",
    "    # 阀值图像上的轮廓位置\n",
    "    cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,\n",
    "                                       cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    # 遍历轮廓\n",
    "    for c in cnts:\n",
    "        # 忽略小轮廓，排除误差\n",
    "        if cv2.contourArea(c) < 300:\n",
    "            continue\n",
    "\n",
    "        # 计算轮廓的边界框，在当前帧中画出该框\n",
    "        (x, y, w, h) = cv2.boundingRect(c)\n",
    "        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)\n",
    "\n",
    "    # 显示当前帧\n",
    "    cv2.imshow(\"frame\", frame)\n",
    "    cv2.imshow(\"frameDelta\", frameDelta)\n",
    "    cv2.imshow(\"thresh\", thresh)\n",
    "\n",
    "    # 保存视频\n",
    "    out1.write(frame)\n",
    "    \n",
    "    # 如果q键被按下，跳出循环\n",
    "    if cv2.waitKey(50) & 0xFF == ord('q'):\n",
    "        break\n",
    "    \n",
    "e2 = cv2.getTickCount()\n",
    "print((e2 - e1) / cv2.getTickFrequency())\n",
    "\n",
    "# 清理资源并关闭打开的窗口\n",
    "out1.release()\n",
    "camera.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三帧差分法\n",
    "\n",
    "　　三帧差法是在相邻帧差法基础上改进的算法，在一定程度上优化了运动物体双边，粗轮廓的现象，相比之下，三帧差法比相邻帧差法更适用于物体移动速度较快的情况，比如道路上车辆的智能监控。\n",
    "　　三帧差法基本实现步骤如下：首先前两帧图像做灰度差，然后当前帧图像与前一帧图像做灰度差，最后1和2的结果图像按位做“与”操作，进行阙值判断和得出运动目标。\n",
    "  \n",
    ">相邻三帧 ➡ △1=absdiff(帧1, 帧2)、△2=absdiff(帧2, 帧3) ➡ bitwise_and(△1, △2) ➡ 灰度图 ➡ 二值化 ➡ (形态学变换) ➡ 轮廓检测 ➡ 过滤轮廓面积\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 184,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "79.5002203\n"
     ]
    }
   ],
   "source": [
    "# 导入必要的软件包\n",
    "import cv2\n",
    "\n",
    "# 视频文件输入初始化\n",
    "filename = \"D:\\OneDrive - business\\git_clone\\OpenCV-Python-Tutorial-master\\data\\street_car.mp4\"\n",
    "camera = cv2.VideoCapture(filename)\n",
    "\n",
    "KERNEL1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))\n",
    "KERNEL2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 3))\n",
    "e1 = cv2.getTickCount()\n",
    "# 初始化当前帧的前两帧,计算第一二帧的不同\n",
    "_, lastFrame1 = camera.read()\n",
    "_, lastFrame2 = camera.read()\n",
    "frameDelta1 = cv2.absdiff(lastFrame1, lastFrame1)  # 帧差一\n",
    "\n",
    "\n",
    "while camera.isOpened():\n",
    "    # 读取下一帧\n",
    "    (ret, frame) = camera.read()\n",
    "\n",
    "    # 如果不能抓取到一帧，说明我们到了视频的结尾\n",
    "    if not ret:\n",
    "        break\n",
    "\n",
    "#     # 调整该帧的大小\n",
    "#     frame = cv2.resize(frame, (500, 400), interpolation=cv2.INTER_CUBIC)\n",
    "\n",
    "    # 计算当前帧和前帧的不同,计算三帧差分\n",
    "    frameDelta2 = cv2.absdiff(lastFrame2, frame)  # 帧差二\n",
    "    thresh = cv2.bitwise_or(frameDelta1, frameDelta2)  # 图像与运算\n",
    "    thresh2 = thresh.copy()\n",
    "\n",
    "    # 当前帧设为下一帧的前帧,前帧设为下一帧的前前帧,帧差二设为帧差一\n",
    "    lastFrame1 = lastFrame2\n",
    "    lastFrame2 = frame.copy()\n",
    "    frameDelta1 = frameDelta2\n",
    "\n",
    "    # 结果转为灰度图\n",
    "    thresh = cv2.cvtColor(thresh, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "    # 图像二值化\n",
    "    thresh = cv2.threshold(thresh, 25, 255, cv2.THRESH_BINARY)[1]\n",
    "\n",
    "    # 开运算\n",
    "    thresh = cv2.erode(thresh, KERNEL1, iterations=1)\n",
    "    thresh = cv2.dilate(thresh, KERNEL2, iterations=3)\n",
    "\n",
    "\n",
    "\n",
    "    # 阀值图像上的轮廓位置\n",
    "    cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    # 遍历轮廓\n",
    "    for c in cnts:\n",
    "        # 忽略小轮廓，排除误差\n",
    "        if cv2.contourArea(c) < 300:\n",
    "            continue\n",
    "\n",
    "        # 计算轮廓的边界框，在当前帧中画出该框\n",
    "        (x, y, w, h) = cv2.boundingRect(c)\n",
    "        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)\n",
    "\n",
    "    # 显示当前帧\n",
    "    cv2.imshow(\"frame\", frame)\n",
    "    cv2.imshow(\"diff\", thresh)\n",
    "    cv2.imshow(\"threst2\", thresh2)\n",
    "    \n",
    "    # 如果q键被按下，跳出循环\n",
    "    if cv2.waitKey(2000) & 0xFF == ord('q'):\n",
    "        break\n",
    "\n",
    "e2 = cv2.getTickCount()\n",
    "print((e2 - e1) / cv2.getTickFrequency())\n",
    "    \n",
    "# 清理资源并关闭打开的窗口\n",
    "camera.release()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# KNN\n",
    "\n",
    "　kNN 可以说是最简单的监督学习分类器了。想法也很简单，就是找出测试数据在特征空间中的最近邻居。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "code_folding": [],
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import cv2 as cv\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "# 设置文件\n",
    "file_test = \"D:\\OneDrive - business\\git_clone\\OpenCV-Python-Tutorial-master\\data\\street_car.mp4\"\n",
    "cap = cv.VideoCapture(0)\n",
    "\n",
    "# 设置变量\n",
    "video_skip = True\n",
    "kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))  # 定义卷积核\n",
    "color_m = (255, 0, 0)\n",
    "\n",
    "# 背景差法\n",
    "knn = cv.createBackgroundSubtractorKNN(history=60, dist2Threshold=False, detectShadows=False)\n",
    "\n",
    "while True:\n",
    "    # 读取一帧\n",
    "    ret, frame = cap.read()\n",
    "    # 如果视频结束，跳出循环\n",
    "    if not ret:\n",
    "        break\n",
    "\n",
    "    # 计算前景掩码\n",
    "    fgmask = knn.apply(frame)\n",
    "    draw1 = cv.threshold(fgmask, 25, 255, cv.THRESH_BINARY)[1]  # 二值化\n",
    "#     print((fgmask==draw1).all())\n",
    "    # 开操作\n",
    "    draw1 = cv.morphologyEx(draw1, cv.MORPH_OPEN, kernel, iterations=1)\n",
    "\n",
    "    # 查找检测物体的轮廓,只检测外轮廓,只需4个点来保存轮廓信息\n",
    "    contours_m, hierarchy_m = cv.findContours(draw1, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    for c in contours_m:\n",
    "        if cv.contourArea(c) < 300:\n",
    "            continue\n",
    "        (x, y, w, h) = cv.boundingRect(c)\n",
    "        cv.rectangle(frame, (x, y), (x + w, y + h), color_m, 2)\n",
    "\n",
    "    cv.imshow(\"source\", frame)\n",
    "    cv.imshow(\"apply\", fgmask)\n",
    "    cv.imshow(\"draw\", draw1)\n",
    "    k = cv.waitKey(30)\n",
    "    if k == ord('q'):\n",
    "        break\n",
    "\n",
    "\n",
    "cap.release()\n",
    "cv.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "heading_collapsed": true
   },
   "source": [
    "# 光流法\n",
    "\n",
    "在实际测试中，虽然它能检测运动目标的整个区域，适用于摄像机静止和运动情况，但大多数的光流计算方法计算量巨大，结构复杂，且易受光照、物体遮挡或图像噪声的影响，鲁棒性差，故一般不被对精度和实时性要求比较高的监控系统所采用\n",
    "\n",
    "- https://zhuanlan.zhihu.com/p/42942198\n",
    "\n",
    "- https://blog.csdn.net/ssybc/article/details/89637951\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "heading_collapsed": true
   },
   "source": [
    "# 其他Demo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "hidden": true
   },
   "source": [
    "## 仅检测是否有入侵\n",
    "\n",
    "通过2帧差法，判断是否大于阈值，进行抓拍保存，不进行动态区域标记"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "hidden": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "http://www.technicdynamic.com/2017/08/28/python-motion-detection-with-opencv-simple/\n",
    "\"\"\"\n",
    "import cv2\n",
    "from datetime import datetime\n",
    "\n",
    "threshold = 110000  # Threshold for triggering \"motion detection\"\n",
    "\n",
    "\n",
    "def diffImg(t0, t1, t2):  # Function to calculate difference between images.\n",
    "    d1 = cv2.absdiff(t2, t1)\n",
    "    d2 = cv2.absdiff(t1, t0)\n",
    "    return cv2.bitwise_and(d1, d2)\n",
    "\n",
    "\n",
    "cam = cv2.VideoCapture(0)  # Lets initialize capture on webcam\n",
    "\n",
    "# Read three images first:\n",
    "t_minus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)\n",
    "t = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)\n",
    "t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)\n",
    "# Lets use a time check so we only 每秒处理一帧\n",
    "timeCheck = datetime.now().second\n",
    "\n",
    "print('frame size: {}'.format(t_minus.shape[0] * t_minus.shape[1]))\n",
    "\n",
    "while True:\n",
    "    ret, frame = cam.read()\n",
    "    cv2.imshow('Indicator', frame)\n",
    "    # every second\n",
    "    if timeCheck != datetime.now().second:\n",
    "        t_minus = t\n",
    "        t = t_plus\n",
    "        t_plus = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)\n",
    "        diff_img = diffImg(t_minus, t, t_plus)\n",
    "        count = cv2.countNonZero(diff_img)\n",
    "        if count > threshold:\n",
    "            print('{}s Detected {}'.format(timeCheck, count))\n",
    "            cv2.imshow('diff', diff_img)\n",
    "#             cv2.imwrite(datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg', dimg)\n",
    "        timeCheck = datetime.now().second\n",
    "\n",
    "    key = cv2.waitKey(10)\n",
    "    if key == ord('q'):\n",
    "        break\n",
    "\n",
    "cam.release()\n",
    "cv2.destroyAllWindows()  # comment to hide window"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "251.8px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
